package pfc.pfcjoc.core;

import static playn.core.PlayN.assets;
import static playn.core.PlayN.graphics;
import static playn.core.PlayN.log;
import static playn.core.PlayN.mouse;

import org.jbox2d.callbacks.DebugDraw;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.World;

import pfc.pfcjoc.entities.*;
import playn.core.AssetWatcher;
import playn.core.CanvasImage;
import playn.core.DebugDrawBox2D;
import playn.core.Image;
import playn.core.ImageLayer;
// import playn.core.Keyboard;
import playn.core.Mouse;
import playn.core.ResourceCallback;
import playn.core.Mouse.ButtonEvent;
import playn.core.Mouse.MotionEvent;
import react.UnitSlot;
import tripleplay.ui.Background;
import tripleplay.ui.Button;
import tripleplay.ui.Group;
import tripleplay.ui.Interface;
import tripleplay.ui.Root;
import tripleplay.ui.Style;
import tripleplay.ui.Styles;
import tripleplay.ui.Stylesheet;
import tripleplay.ui.layout.AxisLayout;

public class EditGameState extends GameState {
  private static final EditGameState instance = new EditGameState();

  private EditLevel level;
  private World world;

  private float scrollX = 0;
  // private float prevScrollX = 0;

  // private float scrollY;

  private static boolean showDebugDraw = false;
  private DebugDrawBox2D debugDraw;
  private ImageLayer debugCanvasLayer;
  private CanvasImage debugCanvasImage;

  private Element elemSelected = null;
  private Element elemHovered = null;
  private Vec2 distMouseToCenterElem;

  private Vec2 prevPointerPos = new Vec2();
  // private Keyboard.Listener keyboard = null;
  private Mouse.Listener mouse = null;

  private Interface iface;
  private Button currentButton;

  public static int CELL_SIZE = 16;
  public static int UI_WIDTH = 40;
  public static int UI_HEIGHT = 370;

  private String blockToAdd = null;
  private final int MOVE = 1;
  private final int ADDBLOCK = 2;
  private final int DELBLOCK = 3;
  private int mode;


  private EditGameState() {
    // Inicialització Box2D
    Boolean doSleep = true;
    Vec2 gravity = new Vec2(0.0f, 10.0f);
    world = new World(gravity, doSleep);
    world.setWarmStarting(true);
    world.setAutoClearForces(true);

    // Init variables
    level = new EditLevel(world);
  }


  public static EditGameState getInstance() {
    return instance;
  }


  public void addGraphics() {
    graphics().rootLayer().add(level.getLevelLayer());
    graphics().rootLayer().add(level.getBgColorLayer());

    if (showDebugDraw) {
      enableDebugDraw();
    }
  }


  public void enableDebugDraw() {
    graphics().rootLayer().add(debugCanvasLayer);
    world.setDebugDraw(debugDraw);
  }


  public void disableDebugDraw() {
    graphics().rootLayer().remove(debugCanvasLayer);
    world.setDebugDraw(null);
  }


  public void onEnteringMode() {
    log().info("Entrant a EDITING");

    level = new EditLevel(world);

    level.loadLevel(new ResourceCallback<Boolean>() {
      @Override
      public void done(Boolean res) {
        // Init of debug drawing
        debugCanvasImage = graphics().createImage(graphics().width(), graphics().height());
        debugCanvasLayer = graphics().createImageLayer(debugCanvasImage);
        debugDraw = new DebugDrawBox2D();
        debugDraw.setCanvas(debugCanvasImage);
        debugDraw.setFlipY(false);
        debugDraw.setStrokeAlpha(150);
        debugDraw.setFillAlpha(75);
        debugDraw.setStrokeWidth(2.0f);
        debugDraw.setFlags(DebugDraw.e_shapeBit | DebugDraw.e_jointBit | DebugDraw.e_aabbBit);
        debugDraw.setCamera(0, 0, pfcjoc.toPix(1f));

        // scrollX = 0;
        addGraphics();
        setControlListeners();
        configInterface();
      }


      @Override
      public void error(Throwable err) {
        log().error("Error loading LEVEL: " + err.getMessage());
      }
    });
  }


  public void onLeavingMode() {
    mouse().setListener(null);
    // Netejem el nivell
    level.removeAll();
  }


  public void enterEditingMode() {
    log().info("Ja estem en EDITING");
  }


  // public void enterPlayingMode() {
  // PlayGameState play = PlayGameState.getInstance();
  // play.setContext(context);
  // context.setCurrentGameState(play);
  // }

  public void enterMenuMode() {
    MenuGameState menu = MenuGameState.getInstance();
    context.setCurrentGameState(menu);
  }


  public void setControlListeners() {
    // MOUSE
    if (mouse == null) {
      mouse = new Mouse.Adapter() {
        // MOUSE WHEEL
        public void onMouseWheelScroll(Mouse.WheelEvent event) {
          scrollX += 2 * event.velocity();
          scrollX = (scrollX > 0) ? 0 : scrollX;
        }


        @Override
        // MOUSE CLICK START
        public void onMouseDown(ButtonEvent event) {
          if (event.x() <= UI_WIDTH && event.y() <= UI_HEIGHT) {
            return;
          }
          Vec2 pointer = new Vec2(event.x(), event.y());
          Vec2 pointerPhy = pfcjoc.toPhy(pointer);

          switch (mode) {
            case MOVE:
              // Si no hi havia cap objecte agafat, es sel·lecciona el de la pos del cursor
              if (elemSelected == null) {
                elemSelected = level.getElemAt(event);
                if (elemSelected != null) {
                  // dist en physic coords Pointer-->BodyCenter
                  float x = pointerPhy.x - scrollX;
                  float y = pointerPhy.y;
                  distMouseToCenterElem = new Vec2(x - elemSelected.getPosPhy().x, y - elemSelected.getPosPhy().y);
                  elemSelected.getLayer().setAlpha(0.5f);
                }
              }
              break;

            case ADDBLOCK:

              break;

            case DELBLOCK:

              break;

            default:
              break;
          }
        }


        // MOUSE CLICK END
        @Override
        public void onMouseUp(ButtonEvent event) {
          if (event.x() <= UI_WIDTH && event.y() <= UI_HEIGHT) {
            return;
          }

          switch (mode) {
            case MOVE:
              // Si s'estava movent algun objecte ja existent es deixa anar
              if (elemSelected != null) {
                elemSelected.getLayer().setAlpha(1f);
                elemSelected = null;
              }
              break;

            case ADDBLOCK:
              if (elemSelected != null) {
                elemSelected.getLayer().setAlpha(1f);
                elemSelected = null;
              }
              break;

            case DELBLOCK:
              elemSelected = level.getElemAt(event);
              if (elemSelected != null) {
                level.remove(elemSelected);
                elemSelected = null;
              }
              break;

            default:
              break;
          }
        }


        // MOUSE MOVEMENT
        @Override
        public void onMouseMove(MotionEvent event) {
          // Si mouse event per sobre de UI (tractada per pointer events)
          if (event.x() <= UI_WIDTH && event.y() <= UI_HEIGHT) {
            if (elemHovered != null) {
              elemHovered.getLayer().setAlpha(1.0f);
              elemHovered = null;
            }
            switch (mode) {
            // Si movem cursor sobre UI amb block per afegir el descartem
              case ADDBLOCK:
                level.remove(elemSelected);
                elemSelected = null;
                break;

              default:
                break;
            }
            return;
          }

          // Si mouse event fora de la UI
          Vec2 pointer = new Vec2(event.x(), event.y());
          Vec2 pointerPhy = pfcjoc.toPhy(pointer);

          switch (mode) {
            case MOVE:
              gestioHover(event);
              if (elemSelected != null) {
                float x = pointerPhy.x - scrollX;
                float y = pointerPhy.y;
                Vec2 pos = new Vec2(x - distMouseToCenterElem.x, y - distMouseToCenterElem.y);
                pos = snapToGrid(pfcjoc.toPix(pos), CELL_SIZE);
                pos = pfcjoc.toPhy(pos);

                elemSelected.setPosPhy(pos);
              }
              break;

            case DELBLOCK:
              gestioHover(event);
              break;

            case ADDBLOCK:
              if (elemSelected == null) {
                elemSelected = level.add(pfcjoc.toPix(prevPointerPos), blockToAdd);
                elemSelected.getLayer().setAlpha(0.5f);
              }
              float x = pointerPhy.x - scrollX;
              float y = pointerPhy.y;
              Vec2 pos = new Vec2(x, y);
              pos = snapToGrid(pfcjoc.toPix(pos), CELL_SIZE);
              pos = pfcjoc.toPhy(pos);

              elemSelected.setPosPhy(pos);
              break;

            default:
              break;
          }

          prevPointerPos.x = pointerPhy.x - scrollX;
          prevPointerPos.y = pointerPhy.y;
        }
      };
    }
    mouse().setListener(mouse);
  }


  public void gestioHover(MotionEvent event) {
    if (elemSelected == null) {
      Element newElemHovered = level.getElemAt(event);
      // Mouse sobre un element nou
      if (newElemHovered != null) {
        if (elemHovered == null) {
          elemHovered = newElemHovered;
        }
        elemHovered.getLayer().setAlpha(1.0f);
        newElemHovered.getLayer().setAlpha(0.5f);
        elemHovered = newElemHovered;
      }
      // Mouse sobre CAP nou element
      else {
        if (elemHovered != null) {
          elemHovered.getLayer().setAlpha(1.0f);
          elemHovered = null;
        }
      }
    }
  }


  // Boto de la UI marcat
  private void setCurrentButton(Button b) {
    // Si hi havia un boto marcat el desmarquem
    if (currentButton != null) {
      // currentButton.setStyles(b.styles());
      currentButton.setStyles(Styles.none().add(Style.BACKGROUND.is(Background.solid(0xFFFFFFFF).inset(5))).addSelected(
          Style.BACKGROUND.is(Background.solid(0xFFCCCCCC).inset(5))));

    }
    currentButton = b;
    currentButton.addStyles(Styles.make(Style.BACKGROUND.is(Background.solid(0xFF8D9094).inset(5))));
  }


  private void putAddElemButton(final String elemName, Group buttons, AssetWatcher assetWatcher) {
    final Button addElemButton = new Button();
    String path = new String("images/" + elemName + "_icon.png");
    Image addElemIcon = assets().getImage(path);
    assetWatcher.add(addElemIcon);
    addElemButton.setIcon(addElemIcon);
    buttons.add(addElemButton);
    addElemButton.clicked().connect(new UnitSlot() {
      @Override
      public void onEmit() {
        setCurrentButton(addElemButton);
        mode = ADDBLOCK;
        blockToAdd = elemName;
      }
    });
  }


  private void configInterface() {
    // Mode per defecte al entrar a editor
    mode = MOVE;

    if (iface == null) {
      iface = new Interface();
    }

    // pointer().setListener(iface.plistener);

    Styles buttonStyles = Styles.none().add(Style.BACKGROUND.is(Background.solid(0xFFFFFFFF).inset(5))).addSelected(
        Style.BACKGROUND.is(Background.solid(0xFFCCCCCC).inset(5)));
    Stylesheet rootSheet = Stylesheet.builder().add(Button.class, buttonStyles).create();

    final Root root = iface.createRoot(AxisLayout.vertical().gap(5), rootSheet);
    root.setSize(UI_WIDTH, UI_HEIGHT);
    root.addStyles(Styles.make(Style.BACKGROUND.is(Background.blank())));

    AssetWatcher assetWatcher = new AssetWatcher(new AssetWatcher.Listener() {
      @Override
      public void done() {
        graphics().rootLayer().add(root.layer);
      }


      @Override
      public void error(Throwable e) {
        log().error("Error loading images!", e);
      }
    });

    Group buttons;
    root.add(buttons = new Group(AxisLayout.vertical().offStretch().gap(5)));

    String path;
    // BOTO MOVE
    final Button moveButton = new Button();
    path = new String("images/move_icon.png");
    Image moveIcon = assets().getImage(path);
    assetWatcher.add(moveIcon);
    moveButton.setIcon(moveIcon);
    buttons.add(moveButton);
    moveButton.clicked().connect(new UnitSlot() {
      @Override
      public void onEmit() {
        setCurrentButton(moveButton);
        mode = MOVE;
      }
    });

    // Per defecte MOVE
    setCurrentButton(moveButton);

    putAddElemButton("block", buttons, assetWatcher);
    putAddElemButton("groundblock", buttons, assetWatcher);
    putAddElemButton("tube", buttons, assetWatcher);
    putAddElemButton("bush", buttons, assetWatcher);
    putAddElemButton("ramp", buttons, assetWatcher);
    putAddElemButton("toad", buttons, assetWatcher);
    putAddElemButton("woodblock", buttons, assetWatcher);

    // BOTO SAVE LEVEL
    Button saveLevelButton = new Button();
    path = new String("images/save_icon.png");
    Image saveLevelIcon = assets().getImage(path);
    assetWatcher.add(saveLevelIcon);
    saveLevelButton.setIcon(saveLevelIcon);
    buttons.add(saveLevelButton);
    saveLevelButton.clicked().connect(new UnitSlot() {
      @Override
      public void onEmit() {
        level.saveLevel();
      }
    });

    // BOTO DELETE
    final Button deleteButton = new Button();
    path = new String("images/delete_icon.png");
    Image deleteIcon = assets().getImage(path);
    assetWatcher.add(deleteIcon);
    deleteButton.setIcon(deleteIcon);
    buttons.add(deleteButton);
    deleteButton.clicked().connect(new UnitSlot() {
      @Override
      public void onEmit() {
        setCurrentButton(deleteButton);
        mode = DELBLOCK;
        blockToAdd = null;
      }
    });

    // BOTO DEBUG
    Button debugButton = new Button();
    path = new String("images/debug_icon.png");
    Image debugIcon = assets().getImage(path);
    assetWatcher.add(debugIcon);
    debugButton.setIcon(debugIcon);
    buttons.add(debugButton);
    debugButton.clicked().connect(new UnitSlot() {
      @Override
      public void onEmit() {
        toggleDebugDraw();
      }
    });

    // BOTO EXIT
    Button exitButton = new Button();
    path = new String("images/exit_icon.png");
    Image exitIcon = assets().getImage(path);
    assetWatcher.add(exitIcon);
    exitButton.setIcon(exitIcon);
    buttons.add(exitButton);
    exitButton.clicked().connect(new UnitSlot() {
      @Override
      public void onEmit() {
        enterMenuMode();
      }
    });

    // Iniciem aw, quan tots els assets s'hagin carregat es farà callback Done()
    assetWatcher.start();
  }


  public Vec2 snapToGrid(Vec2 pos, int cellSize) {
    Vec2 newPos = new Vec2();
    // newPos.x = pos.x - (pos.x % cellSize) + CELL_SIZE / 2;
    // newPos.y = pos.y - (pos.y % cellSize) + CELL_SIZE / 2 - 8;
    newPos.x = pos.x - (pos.x % cellSize) + CELL_SIZE / 2 - 8;
    newPos.y = pos.y - (pos.y % cellSize) + CELL_SIZE / 2;// - 8;
    return newPos;
  }


  public void toggleDebugDraw() {
    showDebugDraw = !showDebugDraw;
    if (showDebugDraw) {
      enableDebugDraw();
    }
    else {
      disableDebugDraw();
    }
  }


  // SCROLLING = CAMERA SIMULATION
  public void scrollCamera(float alpha) {
    float sx = scrollX;
    level.getLevelLayer().setTranslation(pfcjoc.toPix(sx), 0);
    // debugCanvasLayer.setTranslation(pfcjoc.toPix(sx), 0);
    debugDraw.setCamera(-sx, 0, pfcjoc.toPix(1f));
  }


  public void paint(float alpha) {
    if (!level.isLoaded()) return;
    scrollCamera(alpha);
    // PAINTING
    level.paint(alpha);

    // DEBUG DRAWING
    if (showDebugDraw) {
      // Debug Layer s'ha de pintar per damunt de TOT
      // debugCanvasLayer.setDepth(100);
      debugDraw.getCanvas().clear();
      world.drawDebugData();
    }

    if (iface != null) {
      iface.paint(alpha);
    }
  }


  public void update(float delta) {
    if (!level.isLoaded()) return;
    level.update(delta);

    if (iface != null) {
      iface.update(delta);
    }
  }
}
